#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "input.h"

/* 
 * This module parses the input into a command structure
 * that is passed to the command module.
*/


/* initialize struc */
static Cmdline* initCmdline()
{
   Cmdline* tmp = (Cmdline* ) malloc(sizeof(Cmdline));
   tmp->runInBackground = 0;
   tmp->exitAfter = 0;
   tmp->argc = 0;
   tmp->errCode = 0;
   tmp->stdout_dest = NULL;
   tmp->stdin_src = NULL;
   return tmp;
}

/* destroy struc */
void destroy(Cmdline* cmdl)
{
    if(cmdl->argc > 0)
       free(cmdl->argv);
    if(NULL != cmdl->cmd)
       free(cmdl->cmd);
    if(NULL != cmdl->stdout_dest)
       free(cmdl->stdout_dest);
    if(NULL != cmdl->stdin_src)
       free(cmdl->stdin_src);
    free(cmdl);
}

/* check for shell only commands */
static unsigned int checkShellCmd(char* cmd_buff, int buff_cnt, Cmdline* curr_cmd)
{
    /* TODO make this better 
     * 'exit' */
    if (4 == buff_cnt
        && 'e' == cmd_buff[0]
	&& 'x' == cmd_buff[1]
	&& 'i' == cmd_buff[2]
	&& 't' == cmd_buff[3]){ 
           free(cmd_buff);
           destroy(curr_cmd);
           exit(EXIT_SUCCESS);
    
    }
    return 0;
}

/* user message to explain illegal chars */
static void charExplain(){
   fprintf(stderr, "Certain characters are blocked since they are not interpreted in a user expected manner.\n");
   fprintf(stderr, "Characters such as | (pipe), ; or && (multiple commands on single line), \\ (char escaping), etc\n");
}

/* check command line for illegal chars */
static int isCmdLineCharIllegal(char c){
    int i;
    for(i = 0; i<strlen(ILLEGALLINECHARS); i++){
        if(ILLEGALLINECHARS[i] == c){
	   charExplain();
	   return 1;
	}
    }
    return 0;
}

/* check string for characters in another string */
static int isStrIllegal(char* str, char* charset){
    int i, j;
    for(i=0; i<strlen(str); i++){
       char c = str[i];
       for(j = 0; j<strlen(charset); j++){
          if(charset[j] == c){
	     
	     /* special trailing & */
	     if(strlen(str)-1 == i 
	        && '&' == c
		&& strlen(str)>1)
	        return 0;
             
	     charExplain();
	     return 1;
          }
       }
    }
    return 0;
}

/* writes the command to the struc
 * this function may modify the buff_cnt 
 * or the position of the next char
 * in the buffer string
 * */
static int cmdToStruct(char* cmd_buff, int* buff_cnt, Cmdline* curr_cmd){
	  int bufferChars;

          if('&' == cmd_buff[(*buff_cnt)-1])
	     (*buff_cnt)--; /* ignore trailing & */

          bufferChars = *buff_cnt;

          /* test for illegal command chars */
          cmd_buff[(*buff_cnt)]='\0';
	  if(isStrIllegal(cmd_buff,ILLEGALCMDCHARS)){
	      fprintf(stderr, "Invalid char in command:%s\n", cmd_buff); 
              charExplain();
	      curr_cmd->errCode = 4;
	      return 1;
	  }

          /* transfer string in buffer to structure
	   *            * and create null string terminator
	   *                      */
	   curr_cmd->cmd = (char*) malloc(sizeof(char)*bufferChars+sizeof(char)); /* add 1 char for \0 */
	   if(NULL == curr_cmd->cmd){
	      fprintf(stderr, "Failed to allocate command string.\n");
	      exit(EXIT_FAILURE);
	   }
	   strncpy(curr_cmd->cmd, cmd_buff, bufferChars);
	   curr_cmd->cmd[bufferChars] = '\0'; /* (-1+1) zero index + for null */

	   return 0;
}

/* writes arg to the struct */
static void addToArgArray(Cmdline* curr_cmd, char* newarg, int argChars){
      curr_cmd->argc++;

      if(1 == curr_cmd->argc){
         curr_cmd->argv = (char** ) malloc(sizeof(char*));
	 if(NULL == curr_cmd->argv){
	    fprintf(stderr, "Failed to allocate memory for argument\n");
	    exit(EXIT_FAILURE);
	 }
      } else {
         curr_cmd->argv = realloc(curr_cmd->argv, sizeof(char*)*(curr_cmd->argc));
         if(NULL == curr_cmd->argv){
            fprintf(stderr, "Failed to reallocate memory for additonal argument\n");
            exit(EXIT_FAILURE);
         }
      }

      curr_cmd->argv[curr_cmd->argc-1] = (char*) malloc(sizeof(char)*(argChars)+sizeof(char));
      if(NULL == curr_cmd->argv[curr_cmd->argc-1]){
         fprintf(stderr, "Failed to malloc arugment string\n");
      }

      strncpy(curr_cmd->argv[curr_cmd->argc-1], newarg, argChars);
      ((char*) curr_cmd->argv[curr_cmd->argc-1])[argChars] = '\0'; /* (-1+1) zero index + for null */

}

/* parses arguements and saves to arg array */
static int argToStruct(char* cmd_buff, int* buff_cnt, Cmdline* curr_cmd){
   int i, offset;
   int bufferChars;
   int argChars = 0;

   if('&' == cmd_buff[(*buff_cnt)-1])
      (*buff_cnt)--; /* ignore trailing & */

   bufferChars = (*buff_cnt);

   offset=strlen(curr_cmd->cmd); /* char after end of the command */

   /* search previous arguments for their size too */
   for(i=0;i<curr_cmd->argc;i++){
      offset+=strlen(curr_cmd->argv[i]); /* point past previous arguemets */
   }

   /* add in any redirects */
   if(NULL != curr_cmd->stdout_dest){
      offset+= 1; /* '>' */
      offset+= strlen(curr_cmd->stdout_dest);
   }
   if(NULL != curr_cmd->stdin_src){
      offset+= 1; /* '<' */
      offset+= strlen(curr_cmd->stdin_src);
   }

   /* get chars needed for malloc */
   argChars = bufferChars-offset;

   /* check if redirect */
   if('>' == cmd_buff[offset] || '<' == cmd_buff[offset]){
      offset++; /* ignore redirect char */
      argChars = bufferChars-offset;

      /* remove empty arg */
      if(0 == argChars) {
	 return 0;
      }

      /* test for illegal arg chars */
      cmd_buff[(*buff_cnt)]='\0';
      if(isStrIllegal(&cmd_buff[offset],ILLEGALCMDCHARS)){
         curr_cmd->errCode = 6;
         fprintf(stderr, "Invalid char in redirect:%s\n", &cmd_buff[offset]);
         return 1;
      }

      switch(cmd_buff[offset-1]){ /* just incremented above */
         case '<':
	    /* limit 1 redirect */
	    if(NULL!=curr_cmd->stdin_src)
	       break;
	    curr_cmd->stdin_src = (char*) malloc(sizeof(char)*argChars+sizeof(char));
            if(NULL==curr_cmd->stdin_src){
	       fprintf(stderr, "Could not malloc for stdin_src redirect\n");
	       exit(EXIT_FAILURE);
	    }
	    strncpy(curr_cmd->stdin_src, &cmd_buff[offset], argChars);
	    curr_cmd->stdin_src[argChars] = '\0';
	    break;
	 case '>':
	    /* limit 1 redirect */
	    if(NULL!=curr_cmd->stdout_dest)
	       break;
	    curr_cmd->stdout_dest = (char*) malloc(sizeof(char)*argChars+sizeof(char));
            if(NULL==curr_cmd->stdout_dest){
	       fprintf(stderr, "Could not malloc for stdout_dest redirect\n");
	       exit(EXIT_FAILURE);
	    }
            strncpy(curr_cmd->stdout_dest, &cmd_buff[offset], argChars);
	    curr_cmd->stdout_dest[argChars] = '\0';
	    break;
      }

      return 0;
   }


   /* remove empty arg really just removes multiple spaces '   ' */
   if(0 == argChars) {
       return 0;
   }

   /* test for illegal arg chars */
   cmd_buff[(*buff_cnt)]='\0';
   if(isStrIllegal(&cmd_buff[offset],ILLEGALARGCHARS)){
       curr_cmd->errCode = 5;
       fprintf(stderr, "Invalid char in argument:%s\n", &cmd_buff[offset]);
       return 1;
   }

   /* copy out argument into struct */
   addToArgArray(curr_cmd, &cmd_buff[offset], argChars);

   return 0;
}


/* returns a command parsed into the struct */
Cmdline* getCommand()
{
   /* buff_cnt points to the next free char in the command string buffer 
    * buff_multi increases the command buffer an order of mag if 
    * string buffer fills */
   int c, buff_cnt, buff_multi;
   int break_n_run = 0; /* breaks getchar loop for ; or \n */
   int inRedirect = 0; /* skip argument parseing when parsing redirect */
   unsigned int inputChars = 0;
   char* cmd_buff; /* String buffer for command line */
  
   Cmdline* curr_cmd = initCmdline();

   /*prompt*/
   printf("%s",PROMPT);
   
   fflush(stdout);
   /*setbuf(stdin, NULL);*/

   /* setup initial buffer */
   buff_cnt = 0;
   buff_multi = 1; /* Times buffer has been expanded */
   
   cmd_buff = (char* ) malloc(sizeof(char)*CMDBUFFSIZE*buff_multi+sizeof(char)); /* initial string buff */
   if( NULL == cmd_buff ) {
      fprintf(stderr, "Cannot allocate memory for command line buffer.\n");
      exit(EXIT_FAILURE);
   }

   
   while(!break_n_run && (c = getchar())){
      inputChars++;

      /* +1 for \n */
      if(inputChars > MAXCMDLENGTH+1){
          fprintf(stderr, "Command line length exceeded, >%d chars.\n", MAXCMDLENGTH);
	  exit(EXIT_FAILURE);
      }

      /* incease buffer if required. */
      if(buff_cnt>CMDBUFFSIZE*buff_multi){
         buff_multi++;
         cmd_buff = (char *) realloc(cmd_buff, sizeof(char)*CMDBUFFSIZE*buff_multi+sizeof(char));
	 if(NULL == cmd_buff){
	     fprintf(stderr, "Failed to increase command line buffer.\n");
	 }
      }
      
      switch(c){
         case EOF:
	     /* all done */
	     printf("EOF detected, exiting.\n");
	     curr_cmd->exitAfter = 1;
	     /* fall through */
         case '\n':
	    if(NULL!=curr_cmd->cmd){
	       argToStruct(cmd_buff, &buff_cnt, curr_cmd);
	    }
	    break_n_run = 1;
	    break;
	 case ' ':
	      /* remove leading spaces */
	      if(0 == buff_cnt)
	          break;
	      /* fall through */
	 default:
	   /* Character filters */
	     /* ignore if contains non-printable */
	     if(!isprint(c) && !curr_cmd->errCode){
		fprintf(stderr, "Error: Invalid non-printable character!\n");
	        curr_cmd->errCode = 1;
	     }
	     if(isCmdLineCharIllegal(c) && !curr_cmd->errCode){
	        fprintf(stderr, "Error: Illegal command line character:%c\n", c);
                curr_cmd->errCode = 2;
	     }
	   /* End Character filters */
	     
	    if(!curr_cmd->errCode){

                 /* command line pre-character buffer */
		 /* The character is not yet in the buffer */
		 switch(c){
		    case '&':
			curr_cmd->runInBackground = 1;
			inRedirect = 0;
		        break;
		    case '>':
		        /* fall */
		    case '<':
			inRedirect = 1;
			break;
		    default:
		        break;
		 }
		 
                 if(!break_n_run){

                    /* put char in buffer */
                    cmd_buff[buff_cnt] = c;
		    buff_cnt++; /* points to next open char element in buffer */

		    /* command-line post-character buffer */
		    switch(c){
                       /* these cases are characters that are considered
			* argument delimiters */
		       case ' ':
  		          buff_cnt--; /* erase space delimiter char from buffer */

			  if('>' == cmd_buff[buff_cnt-1]
			     || '<' == cmd_buff[buff_cnt-1] ){
			     /* basically removes the first space
			      * after a redirect char < > and doesn't
			      * consider it an argument delimiter */
			     break;
			  }
                          
                          /* command / argument delimiting */
			  if(' ' != cmd_buff[buff_cnt-1]){

			     if(NULL!=curr_cmd->cmd) {
			        argToStruct(cmd_buff, &buff_cnt, curr_cmd);
			     } else {
			        /* rejects with 1 if illegal chars */
			        if(cmdToStruct(cmd_buff, &buff_cnt, curr_cmd)){
				    break_n_run = 1;
				}
			     }
			  }
			  break;
		    }

		 }
	     }
	     break;
      }

   }

   /*check for empty buffer */
   if(0 == buff_cnt) 
   {
       curr_cmd->errCode = 3;
   }

   if(!curr_cmd->errCode){
        
       /* Simple case no args */
       if(NULL==curr_cmd->cmd){
          /* handle exit */
          checkShellCmd(cmd_buff, buff_cnt, curr_cmd);

	  /*write command name */
	  /* rejects with 1 if illegal chars */
	  if(cmdToStruct(cmd_buff, &buff_cnt, curr_cmd)){
	     curr_cmd->errCode = 4;
	  };
       }
   }

   /* clean up buffer */
   free(cmd_buff);

   /* add required NULL argument to argv */
   if(!curr_cmd->errCode)
   {
      addToArgArray(curr_cmd, (char*) NULL, 0); 
   }

   return curr_cmd;
}
